package index.planner;

import static storage.record.Schema.FALSE;
import index.Index;
import index.metadata.IndexInfo;

import java.util.List;
import java.util.Map;

import compiler.absyn.Constant;
import compiler.absyn.IntConstant;
import compiler.absyn.OpExp;
import compiler.absyn.StringConstant;
import engine.planner.CreateIndexData;
import engine.planner.CreateTableData;
import engine.planner.CreateViewData;
import engine.planner.DeleteData;
import engine.planner.InsertData;
import engine.planner.ModifyData;
import engine.planner.UpdatePlanner;
import engine.query.Plan;
import engine.query.Scan;
import engine.query.SelectPlan;
import engine.query.SelectScan;
import engine.query.TablePlan;
import engine.query.TableScan;
import engine.query.UpdateScan;

import server.starDB;
import storage.record.RID;
import storage.record.Schema;
import storage.tx.Transaction;
public class IndexUpdatePlanner implements UpdatePlanner {
	
	public int executeInsert(InsertData data, Transaction tx) {
		Plan plan = new TablePlan(data.tableName(), tx);

		// first, insert the record
		TableScan s = (TableScan) plan.open();
		if(checkKeyDup(s,data, tx)){
			System.err.println("key dup error!");
			return -1;
		}
		if (checkNull(s, data)) {
			System.err.println("not null error!");
			return -2;
		}
		if (!checkFKeyExist(s, data, tx)) {
			System.err.println("fkey not exist!");
			return -3;
		}
		if(!satisfyCheck(s,data)){
			System.err.println("not satisfy the check condition!");
			return -4;
		}
		s.insert();
		RID rid = s.getRid();

		// then modify each field, inserting an index record if appropriate
		Map<String,IndexInfo> indexes = starDB.mdMgr().getIndexInfo(data.tableName(), tx);
		for (String fldname : data.fields()) {
			Constant val = data.insertInfo.get(fldname);
			s.setVal(fldname, val);
			IndexInfo ii = indexes.get(fldname);
			if (ii != null) {
				Index idx = ii.open();
 				idx.insert(val, rid);
				idx.close();
			}
		}
		s.close();
		return 0;
	}
	
	public int executeDelete(DeleteData data, Transaction tx) {
		String tblname = data.tableName();
		Plan p = new TablePlan(tblname, tx);
		TableScan ts = (TableScan)p.open();
		p = new SelectPlan(p, data.pred());
		Map<String,IndexInfo> indexes = starDB.mdMgr().getIndexInfo(tblname, tx);
		UpdateScan s = (UpdateScan) p.open();
		if(checkRkeyExit(s,ts,tx)){
			System.err.println("referenced key exist!");
			return -5;
		}
		int count = 0;
		while(s.next()) {
			// first, delete the record's RID from every index
			RID rid = s.getRid();
			for (String fldname : indexes.keySet()) {
				Constant val = s.getVal(fldname);
				Index idx = indexes.get(fldname).open();
				idx.delete(val, rid);
				idx.close();
			}
			// then delete the record
			s.delete();
			count++;
		}
		s.close();
		return count;
	}

	public int executeModify(ModifyData data, Transaction tx) {
		String tblname = data.tableName();
		String fldname = data.targetField();
		Plan p = new TablePlan(tblname, tx);
		TableScan ts = (TableScan)p.open();
		if (!checkFKeyExist((TableScan) p.open(), data, tx)) {
			System.err.println("fkey not exist!");
			return -3;
		}
		if (!satisfyCheck((TableScan) p.open(), data)){
			System.err.println("not satisfy the check condition!");
			return -4;
		}
		p = new SelectPlan(p, data.pred());
		if (checkRKeyExist(ts,(SelectScan) p.open(), data, tx)){
			System.err.println("referenced key exist!");
			return -5;
		}

		IndexInfo ii = starDB.mdMgr().getIndexInfo(tblname, tx).get(fldname);
		Index idx = (ii == null) ? null : ii.open();

		UpdateScan s = (UpdateScan) p.open();
		int count = 0;
		while(s.next()) {
			// first, update the record
			Constant newval = data.newValue().evaluate(s);
			Constant oldval = s.getVal(fldname);
			s.setVal(data.targetField(), newval);

			// then update the appropriate index, if it exists
			if (idx != null) {
				RID rid = s.getRid();
				idx.delete(oldval, rid);
				idx.insert(newval, rid);
			}
			count++;
		}
		if (idx != null) idx.close();
		s.close();
		return count;
	}
	
	public int executeCreateTable(CreateTableData data, Transaction tx) {
		if (!checkFKeyInit(data,tx)){
			System.err.println("foreign key check error!");
			return -6;
		}
		starDB.mdMgr().createTable(data.tableName(), data.newSchema(), tx);
		Schema sch = data.newSchema();
		List<String> keySet = sch.getKeyset();
		for(String fldname:keySet)
			starDB.mdMgr().createIndex(data.tableName(), fldname, tx);
		List<String> fkeyset = sch.getFKeyset();
		for(String fldname:fkeyset)
			starDB.mdMgr().createIndex(data.tableName(), fldname, tx);
		return 0;
	}

	public int executeCreateView(CreateViewData data, Transaction tx) {
		starDB.mdMgr().createView(data.viewName(), data.viewDef(), tx);
		return 0;
	}
	
	public int executeCreateIndex(CreateIndexData data, Transaction tx) {
		starDB.mdMgr().createIndex(data.tableName(), data.fieldName(),tx);
		return 0;
	}
	
	private boolean checkFKeyInit(CreateTableData data, Transaction tx){
		Schema sch = data.newSchema();
		List<String> fkset = sch.getFKeyset();
		for(String fldname:fkset){
			String fktblname = sch.fkeytblname(fldname);
			String fkfldname = sch.fkeyfldname(fldname);
			TablePlan p = new TablePlan(fktblname, tx);
			Schema s = p.schema();
			if(!s.hasField(fkfldname) || s.iskey(fkfldname) == FALSE)
				return false;
		}
		for (String fldname:fkset){
			String fktblname = sch.fkeytblname(fldname);
			String fkfldname = sch.fkeyfldname(fldname);
			Transaction ts = new Transaction();
			starDB.mdMgr().refreshSchema(fktblname, fkfldname, data.tableName(), fldname, ts);
			ts.commit();
//			TablePlan p = new TablePlan(fktblname, tx);
//			Schema s = p.schema();
//			s.setReferenced(fkfldname, data.tableName(), fldname);
//			System.out.println("@@"+fktblname +" "+ fkfldname +" setReferenced: "+data.tableName()+" "+fldname);
		}
		return true;
	}
	
	private boolean checkKeyDup(TableScan us, InsertData data, Transaction tx) {
		List<String> keyset = us.getKeyFieldSet();
		int flag;
		if (keyset.isEmpty()) {
//			System.out.println("isEmpty");
			return false;
		}
		Map<String,IndexInfo> indexes = starDB.mdMgr().getIndexInfo(data.tableName(), tx);
		for(String fldname : keyset){
			Index idx = indexes.get(fldname).open();
			Constant val = data.insertInfo.get(fldname);
			idx.beforeFirst(val);
			if(idx.next()){
				idx.close();
				return true;
			}
			idx.close();
		}
		
//		while (us.next()) {
//			flag = 1;
//			for (String kf : keyset) {
////				System.out.println("Keyset:"+keyset);
//				Constant c = us.getVal(kf);
//				Constant v = data.insertInfo.get(kf);
//				if (c.equals(v))
//					flag = 0;
//				else
//					flag = 1;
//			}
//			if (flag == 0) {
//				return true;
//			}
//		}
		return false;
	}
	
	private boolean checkNull(TableScan us, InsertData data) {
		List<String> nullset = us.getNotNullFieldSet();
		for (String s : nullset) {
			if (data.insertInfo.get(s) == null) {
				return true;
			}
		}
		return false;
	}
	
	private boolean checkFKeyExist(TableScan us, InsertData data, Transaction tx) {
		List<String> fkeyset = us.getFKeyset();
//		if(fkeyset.size()==0){
//			return true;
//		}
		for (String fldname : fkeyset) {
			Constant v = data.insertInfo.get(fldname);
			String fkeytblname = us.fkeytblname(fldname);
			String fkeyfldname = us.fkeyfldname(fldname);
			Map<String,IndexInfo> indexes = starDB.mdMgr().getIndexInfo(fkeytblname, tx);
			Index idx = indexes.get(fkeyfldname).open();
			idx.beforeFirst(v);
			if(!idx.next())
				return false;
//			TablePlan p = new TablePlan(fkeytblname, tx);
//			TableScan s = (TableScan) p.open();
//			while (s.next()) {
//				Constant c = s.getVal(fkeyfldname);
//				if (c.equals(v))
//					return true;
//			}
		}
		return true;
	}
	
	private boolean checkFKeyExist(TableScan us, ModifyData data, Transaction tx) {
		List<String> fkeyset = us.getFKeyset();
		Constant v = data.newValue().asConstant();
		for (String fldname : fkeyset) {
			String fkeytblname = us.fkeytblname(fldname);
			String fkeyfldname = us.fkeyfldname(fldname);
			Map<String,IndexInfo> indexes = starDB.mdMgr().getIndexInfo(fkeytblname, tx);
			Index idx = indexes.get(fkeyfldname).open();
			idx.beforeFirst(v);
			if(!idx.next())
				return false;
//			TablePlan p = new TablePlan(fkeytblname, tx);
//			TableScan s = (TableScan) p.open();
//			while (s.next()) {
//				Constant c = s.getVal(fkeyfldname);
//				if (c.equals(v))
//					return true;
//			}
		}
		return true;
	}
	
	private boolean checkRKeyExist(TableScan ts,SelectScan ss, ModifyData data, Transaction tx) {
		List<String> rkeyset = ts.getRefset();
//		Constant v = data.newValue().asConstant();
		for(String fldname:rkeyset){
			while(ss.next()){
				Constant v = ss.getVal(fldname);
				String rkeytblname = ts.rkeytblname(fldname);
				String rkeyfldname = ts.rkeyfldname(fldname);
				Map<String,IndexInfo> indexes = starDB.mdMgr().getIndexInfo(rkeytblname, tx);
				Index idx = indexes.get(rkeyfldname).open();
				idx.beforeFirst(v);
				if(idx.next()){
					return true;
				}
			}
			ss.beforeFirst();
		}
		return false;
	}

	private boolean checkRkeyExit(UpdateScan ss,TableScan ts,Transaction tx){
		List<String> rkeyset = ts.getRefset();
		for(String fldname:rkeyset){
			while(ss.next()){
				Constant v = ss.getVal(fldname);
				String rkeytblname = ts.rkeytblname(fldname);
				String rkeyfldname = ts.rkeyfldname(fldname);
				Map<String,IndexInfo> indexes = starDB.mdMgr().getIndexInfo(rkeytblname, tx);
				Index idx = indexes.get(rkeyfldname).open();
				idx.beforeFirst(v);
				if(idx.next()){
					return true;
				}
			}
			ss.beforeFirst();
		}
		return false;
	}
	
	private boolean satisfyCheck(TableScan us, InsertData data){
		List<String> checkset = us.getCheckset();
		for(String fldname:checkset){
			Constant lhsval = data.insertInfo.get(fldname);
			String termstring = us.termstring(fldname);
			String[] split =  termstring.split(",");
			if(split.length!=3)
				return true;
			String fldstring = split[0];
			String opstring = split[1];
			String expstring = split[2];
			Constant rhsval;
			int op;
			if(!fldstring.equals(fldname))
				return true;
			if(expstring.matches("\\d+"))
				rhsval = new IntConstant(Integer.parseInt(expstring));
			else
				rhsval = new StringConstant(expstring);
			if(opstring.matches("\\d+"))
				op = Integer.parseInt(opstring);
			else
				return true;
			if(op == OpExp.EQ){
				return rhsval.equals(lhsval);
			}
			else if(op == OpExp.NEQ){
				return !(rhsval.equals(lhsval));
			}
			else if(op == OpExp.GT){
				if(!(lhsval instanceof IntConstant)
					||! (rhsval instanceof IntConstant)){
					return false;
				}
				else
					return (Integer)lhsval.asJavaVal()>(Integer)rhsval.asJavaVal();
			}
			else if(op == OpExp.LT){
				if(!(lhsval instanceof IntConstant)
					||! (rhsval instanceof IntConstant)){
					return false;
				}
				else
					return (Integer)lhsval.asJavaVal()<(Integer)rhsval.asJavaVal();
			}
			else if(op == OpExp.GE){
				if(!(lhsval instanceof IntConstant)
					||! (rhsval instanceof IntConstant)){
					return false;
				}
				else
					return (Integer)lhsval.asJavaVal()>=(Integer)rhsval.asJavaVal();
			}
			else if(op == OpExp.LE){
				if(!(lhsval instanceof IntConstant)
					||! (rhsval instanceof IntConstant)){
					return false;
				}
				else
					return (Integer)lhsval.asJavaVal()<=(Integer)rhsval.asJavaVal();
			}
			return false;
		}
		return true;
	}
	
	private boolean satisfyCheck(TableScan us, ModifyData data){
		List<String> checkset = us.getCheckset();
		Constant lhsval = data.newValue().asConstant();
		for(String fldname:checkset){
			String termstring = us.termstring(fldname);
			String[] split =  termstring.split(",");
			if(split.length!=3)
				return true;
			String fldstring = split[0];
			String opstring = split[1];
			String expstring = split[2];
			Constant rhsval;
			int op;
			if(!fldstring.equals(fldname))
				return true;
			if(expstring.matches("\\d+"))
				rhsval = new IntConstant(Integer.parseInt(expstring));
			else
				rhsval = new StringConstant(expstring);
			if(opstring.matches("\\d+"))
				op = Integer.parseInt(opstring);
			else
				return true;
			if(op == OpExp.EQ){
				return rhsval.equals(lhsval);
			}
			else if(op == OpExp.NEQ){
				return !(rhsval.equals(lhsval));
			}
			else if(op == OpExp.GT){
				if(!(lhsval instanceof IntConstant)
					||! (rhsval instanceof IntConstant)){
					return false;
				}
				else
					return (Integer)lhsval.asJavaVal()>(Integer)rhsval.asJavaVal();
			}
			else if(op == OpExp.LT){
				if(!(lhsval instanceof IntConstant)
					||! (rhsval instanceof IntConstant)){
					return false;
				}
				else
					return (Integer)lhsval.asJavaVal()<(Integer)rhsval.asJavaVal();
			}
			else if(op == OpExp.GE){
				if(!(lhsval instanceof IntConstant)
					||! (rhsval instanceof IntConstant)){
					return false;
				}
				else
					return (Integer)lhsval.asJavaVal()>=(Integer)rhsval.asJavaVal();
			}
			else if(op == OpExp.LE){
				if(!(lhsval instanceof IntConstant)
					||! (rhsval instanceof IntConstant)){
					return false;
				}
				else
					return (Integer)lhsval.asJavaVal()<=(Integer)rhsval.asJavaVal();
			}
			return false;
		}
		return true;
	}
	
}
